#ifndef __PROTOBUFFIK_STREAM_H_
#define __PROTOBUFFIK_STREAM_H_

#include <vector>
#include "protobuffik_endianness.h"
#include "protobuffik_types.h"


namespace protobuffik
{

typedef std::vector< byte > stream_buffer;


class ostream
{
public:
	ostream( stream_buffer& container, endianness e = protobuffik::PROTOBUFFIK_BIG_ENDIAN ) :
		m_pContainer( &container ),
		m_currentEndianness( get_current_endianness( ) ),
		m_targetEndianness( e )
	{
	}

	ostream& operator<<( uint_8 v )
	{
		(*m_pContainer).push_back( (byte) v );
		return (*this);
	}

	ostream& operator<<( int_8 v )
	{
		(*m_pContainer).push_back( (byte) v );
		return (*this);
	}

	ostream& operator<<( uint_16 v )
	{
		byte* pv = reinterpret_cast< byte* >( &v );
		if( m_currentEndianness == m_targetEndianness )
			store16( pv );
		else
			store16Swap( pv );
		return (*this);
	}

	ostream& operator<<( int_16 v )
	{
		byte* pv = reinterpret_cast< byte* >( &v );
		if( m_currentEndianness == m_targetEndianness )
			store16( pv );
		else
			store16Swap( pv );
		return (*this);
	}

	ostream& operator<<( uint_32 v )
	{
		byte* pv = reinterpret_cast< byte* >( &v );
		if( m_currentEndianness == m_targetEndianness )
			store32( pv );
		else
			store32Swap( pv );
		return (*this);
	}

	ostream& operator<<( int_32 v )
	{
		byte* pv = reinterpret_cast< byte* >( &v );
		if( m_currentEndianness == m_targetEndianness )
			store32( pv );
		else
			store32Swap( pv );
		return (*this);
	}

	ostream& operator<<( uint_64 v )
	{
		byte* pv = reinterpret_cast< byte* >( &v );
		if( m_currentEndianness == m_targetEndianness )
			store64( pv );
		else
			store64Swap( pv );
		return (*this);
	}

	ostream& operator<<( int_64 v )
	{
		byte* pv = reinterpret_cast< byte* >( &v );
		if( m_currentEndianness == m_targetEndianness )
			store64( pv );
		else
			store64Swap( pv );
		return (*this);
	}

	ostream& operator<<( float_32 v )
	{
		byte* pv = reinterpret_cast< byte* >( &v );
		if( m_currentEndianness == m_targetEndianness )
			store32( pv );
		else
			store32Swap( pv );
		return (*this);
	}

	ostream& operator<<( float_64 v )
	{
		byte* pv = reinterpret_cast< byte* >( &v );
		if( m_currentEndianness == m_targetEndianness )
			store64( pv );
		else
			store64Swap( pv );
		return (*this);
	}

private:
	void store16( byte* pv )
	{
		(*m_pContainer).push_back( pv[ 0 ] );
		(*m_pContainer).push_back( pv[ 1 ] );
	}

	void store16Swap( byte* pv )
	{
		(*m_pContainer).push_back( pv[ 1 ] );
		(*m_pContainer).push_back( pv[ 0 ] );
	}

	void store32( byte* pv )
	{
		(*m_pContainer).push_back( pv[ 0 ] );
		(*m_pContainer).push_back( pv[ 1 ] );
		(*m_pContainer).push_back( pv[ 2 ] );
		(*m_pContainer).push_back( pv[ 3 ] );
	}

	void store32Swap( byte* pv )
	{
		(*m_pContainer).push_back( pv[ 3 ] );
		(*m_pContainer).push_back( pv[ 2 ] );
		(*m_pContainer).push_back( pv[ 1 ] );
		(*m_pContainer).push_back( pv[ 0 ] );
	}

	void store64( byte* pv )
	{
		(*m_pContainer).push_back( pv[ 0 ] );
		(*m_pContainer).push_back( pv[ 1 ] );
		(*m_pContainer).push_back( pv[ 2 ] );
		(*m_pContainer).push_back( pv[ 3 ] );
		(*m_pContainer).push_back( pv[ 4 ] );
		(*m_pContainer).push_back( pv[ 5 ] );
		(*m_pContainer).push_back( pv[ 6 ] );
		(*m_pContainer).push_back( pv[ 7 ] );
	}

	void store64Swap( byte* pv )
	{
		(*m_pContainer).push_back( pv[ 7 ] );
		(*m_pContainer).push_back( pv[ 6 ] );
		(*m_pContainer).push_back( pv[ 5 ] );
		(*m_pContainer).push_back( pv[ 4 ] );
		(*m_pContainer).push_back( pv[ 3 ] );
		(*m_pContainer).push_back( pv[ 2 ] );
		(*m_pContainer).push_back( pv[ 1 ] );
		(*m_pContainer).push_back( pv[ 0 ] );
	}

private:
	stream_buffer* m_pContainer;
	endianness m_currentEndianness;
	endianness m_targetEndianness;
};


class istream
{
public:
	istream( stream_buffer& container, size_t startPosition = 0, endianness e = protobuffik::PROTOBUFFIK_BIG_ENDIAN ) :
		m_pContainer( &container ),
		m_currentPosition( startPosition ),
		m_currentEndianness( get_current_endianness( ) ),
		m_targetEndianness( e )
	{
	}

	size_t get_unread_length( ) const
	{
		return (*m_pContainer).size( ) - m_currentPosition;
	}

	istream& operator>>( uint_8& vOut )
	{
		vOut = (uint_8) (*m_pContainer)[ m_currentPosition ];
		m_currentPosition += 1;
		return (*this);
	}

	istream& operator>>( int_8& vOut )
	{
		vOut = (int_8) (*m_pContainer)[ m_currentPosition ];
		m_currentPosition += 1;
		return (*this);
	}

	istream& operator>>( uint_16& vOut )
	{
		byte* pv = reinterpret_cast< byte* >( &vOut );
		if( m_currentEndianness == m_targetEndianness )
			read16( pv );
		else
			read16Swap( pv );
		return (*this);
	}

	istream& operator>>( int_16& vOut )
	{
		byte* pv = reinterpret_cast< byte* >( &vOut );
		if( m_currentEndianness == m_targetEndianness )
			read16( pv );
		else
			read16Swap( pv );
		return (*this);
	}

	istream& operator>>( uint_32& vOut )
	{
		byte* pv = reinterpret_cast< byte* >( &vOut );
		if( m_currentEndianness == m_targetEndianness )
			read32( pv );
		else
			read32Swap( pv );
		return (*this);
	}

	istream& operator>>( int_32& vOut )
	{
		byte* pv = reinterpret_cast< byte* >( &vOut );
		if( m_currentEndianness == m_targetEndianness )
			read32( pv );
		else
			read32Swap( pv );
		return (*this);
	}

	istream& operator>>( uint_64& vOut )
	{
		byte* pv = reinterpret_cast< byte* >( &vOut );
		if( m_currentEndianness == m_targetEndianness )
			read64( pv );
		else
			read64Swap( pv );
		return (*this);
	}

	istream& operator>>( int_64& vOut )
	{
		byte* pv = reinterpret_cast< byte* >( &vOut );
		if( m_currentEndianness == m_targetEndianness )
			read64( pv );
		else
			read64Swap( pv );
		return (*this);
	}

	istream& operator>>( float_32& vOut )
	{
		byte* pv = reinterpret_cast< byte* >( &vOut );
		if( m_currentEndianness == m_targetEndianness )
			read32( pv );
		else
			read32Swap( pv );
		return (*this);
	}

	istream& operator>>( float_64& vOut )
	{
		byte* pv = reinterpret_cast< byte* >( &vOut );
		if( m_currentEndianness == m_targetEndianness )
			read64( pv );
		else
			read64Swap( pv );
		return (*this);
	}

private:
	void read16( byte* pv )
	{
		pv[ 0 ] = (*m_pContainer)[ m_currentPosition + 0 ];
		pv[ 1 ] = (*m_pContainer)[ m_currentPosition + 1 ];
		m_currentPosition += 2;
	}

	void read16Swap( byte* pv )
	{
		pv[ 1 ] = (*m_pContainer)[ m_currentPosition + 0 ];
		pv[ 0 ] = (*m_pContainer)[ m_currentPosition + 1 ];
		m_currentPosition += 2;
	}

	void read32( byte* pv )
	{
		pv[ 0 ] = (*m_pContainer)[ m_currentPosition + 0 ];
		pv[ 1 ] = (*m_pContainer)[ m_currentPosition + 1 ];
		pv[ 2 ] = (*m_pContainer)[ m_currentPosition + 2 ];
		pv[ 3 ] = (*m_pContainer)[ m_currentPosition + 3 ];
		m_currentPosition += 4;
	}

	void read32Swap( byte* pv )
	{
		pv[ 3 ] = (*m_pContainer)[ m_currentPosition + 0 ];
		pv[ 2 ] = (*m_pContainer)[ m_currentPosition + 1 ];
		pv[ 1 ] = (*m_pContainer)[ m_currentPosition + 2 ];
		pv[ 0 ] = (*m_pContainer)[ m_currentPosition + 3 ];
		m_currentPosition += 4;
	}

	void read64( byte* pv )
	{
		pv[ 0 ] = (*m_pContainer)[ m_currentPosition + 0 ];
		pv[ 1 ] = (*m_pContainer)[ m_currentPosition + 1 ];
		pv[ 2 ] = (*m_pContainer)[ m_currentPosition + 2 ];
		pv[ 3 ] = (*m_pContainer)[ m_currentPosition + 3 ];
		pv[ 4 ] = (*m_pContainer)[ m_currentPosition + 4 ];
		pv[ 5 ] = (*m_pContainer)[ m_currentPosition + 5 ];
		pv[ 6 ] = (*m_pContainer)[ m_currentPosition + 6 ];
		pv[ 7 ] = (*m_pContainer)[ m_currentPosition + 7 ];
		m_currentPosition += 8;
	}

	void read64Swap( byte* pv )
	{
		pv[ 7 ] = (*m_pContainer)[ m_currentPosition + 0 ];
		pv[ 6 ] = (*m_pContainer)[ m_currentPosition + 1 ];
		pv[ 5 ] = (*m_pContainer)[ m_currentPosition + 2 ];
		pv[ 4 ] = (*m_pContainer)[ m_currentPosition + 3 ];
		pv[ 3 ] = (*m_pContainer)[ m_currentPosition + 4 ];
		pv[ 2 ] = (*m_pContainer)[ m_currentPosition + 5 ];
		pv[ 1 ] = (*m_pContainer)[ m_currentPosition + 6 ];
		pv[ 0 ] = (*m_pContainer)[ m_currentPosition + 7 ];
		m_currentPosition += 8;
	}

private:
	stream_buffer* m_pContainer;
	size_t m_currentPosition;
	endianness m_currentEndianness;
	endianness m_targetEndianness;
};

};

#endif